package com.google.projectulator;

/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.util.List;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;

import com.google.projectulator.exception.ProcessingException;
import com.google.projectulator.exception.ProjectulatorCollectionException;
import com.google.projectulator.exception.ProjectulatorConfigurationException;
import com.google.projectulator.exception.ProjectulatorException;

/**
 * Main Projectulator mojo class.  Controlls the basic lifecycle of
 * the project manipulation as follows:
 * 
 * Gathers projects to manipulate using a ProjectProcessor designated
 * as a collector.
 * Loops through processors designated to operate on the projects.  For each...
 * Initialize the processor
 * Execute the processor against the projects.
 * 
 * If any errors occur, handling is delegated to an error handler.
 *
 * @goal exec
 * 
 * @phase process-sources
 */
public class ProjectulatorMojo extends AbstractMojo {
	/**
	 * @parameter expression="${project}"
	 */
	private MavenProject project;
	/**
	 * @parameter
	 */
	private List<Param> params;
	/**
	 * @parameter
	 */
	private ProjectCollector collector;
	/**
	 * @parameter
	 */
	private Processor preProcessor;
	/**
	 * @parameter
	 */
	private List<Processor> processors;
	/**
	 * @parameter
	 */
	private Processor postProcessor;
	/**
	 * @parameter
	 */
	private ErrorHandler errorHandler;
	
	private Config config;
	
    public void execute() throws MojoExecutionException {
    	try {
    		ensureAtLeastOneProcessorSpecified();
			loadConfig();
			createDefaultsIfNecessary();
			loadLoggers();
			
			Context context = new Context(project, collector, preProcessor, postProcessor);
			initializeErrorHandler();
			performProjectCollection(context);
			performPreProcessing(context);
			performProjectProcessing(context);
			performPostProcessing(context);
		} catch (RuntimeException exception) {
			getLog().error("Error executing projectulator  mojo", exception);
			throw new MojoExecutionException("Error executing projectulator mojo", exception);
		}
    }

	void initializeErrorHandler() throws MojoExecutionException {
		try {
			initialize(errorHandler, config);
		} catch (ProjectulatorException e) {
			throw new MojoExecutionException("Unable to initialize error handler", e);
		}
	}

	void performProjectProcessing(Context context)
			throws MojoExecutionException {
		try {
			getLog().debug("Executing processors against projects");
			for(ProjectState project : context.getProjects()) {
				getLog().debug("Processing project " + project.getMavenProject().getName());
				project.setStatus(Status.WORKING);
				context.setCurrentProject(project);
				project.loadWithProcessors(processors);
				
				for(ProcessorState processorState: project.getProcessorStates()) {
					getLog().debug("Executing " + processorState.getProcessor().getName() + " against " + project.getMavenProject().getName());
					processorState.setStatus(Status.WORKING);
					context.setCurrentProcessor(processorState);
					initialize(processorState.getProcessor(), config);
					processorState.getProcessor().process(context);
					processorState.setStatus(Status.FINISHED);
					cleanup(processorState.getProcessor(), config);
					if (!context.isContinuing()) {
						context.setContinuing(true);
						break;
					}
				}
				
				project.setStatus(Status.FINISHED);
			}
		} catch (Exception exception) {
			if (context.getCurrentProject() != null) {
				context.getCurrentProject().setStatus(Status.ERROR);
				context.getCurrentProject().setError(exception);
				// context.getCurrentProject().setErrorString(exception.getMessage());
			}
			if (context.getCurrentProcessor() != null) {
				context.getCurrentProcessor().setStatus(Status.ERROR);
				context.getCurrentProcessor().setError(exception);
				// context.getCurrentProcessor().setErrorString(exception.getMessage());
			}
			errorHandler.handle(context, exception);
		}
	}

	void performPostProcessing(final Context context)
			throws MojoExecutionException {
		perform(context, context.getPostProcessorState(), context.getPostProcessorState().getProcessor(), new DelegateAction() {
			public void exec(Context arg0, ProjectulatorDelegate arg1)
					throws Exception {
				getLog().debug("Executing post processor");
				context.getPostProcessorState().getProcessor().process(context);
			}
		});
	}

	void performPreProcessing(final Context context)
			throws MojoExecutionException {
		perform(context, context.getPreProcessorState(), context.getPreProcessorState().getProcessor(), new DelegateAction() {
			public void exec(Context arg0, ProjectulatorDelegate arg1)
					throws Exception {
				getLog().debug("Executing pre processor");
				context.getPreProcessorState().getProcessor().process(context);
			}
		});
	}

	void performProjectCollection(final Context context)
			throws MojoExecutionException {
		perform(context, context.getProjectCollector(), context.getProjectCollector().getCollector(), new DelegateAction() {
			public void exec(Context arg0, ProjectulatorDelegate arg1)
					throws Exception {
				getLog().debug("Collecting projects...");
				context.setProjects(context.getProjectCollector().getCollector().collect(context));
				if (context.getProjects() == null || context.getProjects().size() < 1) {
					throw new ProjectulatorCollectionException();
				}
			}
		});
	}
	
	void perform(Context context, ProcessingState state, ProjectulatorDelegate delegate, DelegateAction action) throws MojoExecutionException {
		try {
			state.setStatus(Status.WORKING);
			initialize(delegate, config);
			action.exec(context, delegate);
			cleanup(delegate, config);
			state.setStatus(Status.FINISHED);
		} catch (Exception e) {
			state.setStatus(Status.ERROR);
			state.setError(e);
			errorHandler.handle(context, e);
		}
	}
    
    void initialize(ProjectulatorDelegate delegate, Config config) throws ProjectulatorException {
    	getLog().debug("Initializing " + delegate.getName());
    	delegate.init(config);
    }
    
    void cleanup(ProjectulatorDelegate delegate, Config config) throws ProjectulatorException {
    	getLog().debug("Cleaning up after " + delegate.getName());
    	delegate.cleanup(config);
    }
    
    void loadLoggers() {
    	getLog().debug("Loading delegates with loggers");
    	collector.setLog(getLog());
    	errorHandler.setLog(getLog());
    	preProcessor.setLog(getLog());
    	postProcessor.setLog(getLog());
    	for(Processor processor : processors) {
    		processor.setLog(getLog());
    	}
    }
    
    void createDefaultsIfNecessary() {
    	if (collector == null) {
    		getLog().debug("Using DefaultProjectCollector by default");
    		collector = new DefaultProjectCollector();
    	}
    	if (errorHandler == null) {
    		getLog().debug("Using DefaultErrorHandler by default");
    		errorHandler = new DefaultErrorHandler();
    	}
    	if (preProcessor == null) {
    		getLog().debug("Using EmptyProcessor as default pre processor");
    		preProcessor = new EmptyProcessor();
    		preProcessor.setName("preProcessor(EmptyProcessor");
    	}
    	if (postProcessor == null) {
    		getLog().debug("Using EmptyProcessor as default post processor");
    		postProcessor = new EmptyProcessor();
    		postProcessor.setName("postProcessor(EmptyProcessor)");
    	}
    }

	void loadConfig() {
		getLog().debug("Loading configuration params");
		config = new Config();
		if (params != null) {
			for(Param param : params) {
    			config.put(param.getName(), param.getValue());
    		}
		}
	}
	
	void ensureAtLeastOneProcessorSpecified() {
		if (processors == null || processors.size() < 1) {
			throw new ProjectulatorConfigurationException("You must configure at least one processor for the gathered projects");
		}
	}
	
	public MavenProject getProject() {
		return project;
	}
	
	public void setProject(MavenProject project) {
		this.project = project;
	}
    
    public List<Param> getParams() {
		return params;
	}
    
    public void setParams(List<Param> params) {
		this.params = params;
	}
    
    public Config getConfig() {
		return config;
	}
    
    public void setConfig(Config config) {
		this.config = config;
	}
    
    public ProjectCollector getCollector() {
		return collector;
	}
    
    public void setCollector(ProjectCollector collector) {
		this.collector = collector;
	}
    
    public Processor getPreProcessor() {
		return preProcessor;
	}
    
    public void setPreProcessor(Processor preProcessor) {
		this.preProcessor = preProcessor;
	}
    
    public List<Processor> getProcessors() {
		return processors;
	}
    
    public void setProcessors(List<Processor> processors) {
		this.processors = processors;
	}
    
    public Processor getPostProcessor() {
		return postProcessor;
	}
    
    public void setPostProcessor(Processor postProcessor) {
		this.postProcessor = postProcessor;
	}
    
    public ErrorHandler getErrorHandler() {
		return errorHandler;
	}
    
    public void setErrorHandler(ErrorHandler errorHandler) {
		this.errorHandler = errorHandler;
	}
    
    static interface DelegateAction {
    	void exec(Context context, ProjectulatorDelegate delegate) throws Exception;
    }
}
